﻿using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using SimioAPI;
using SimioAPI.Extensions;
using System.Diagnostics;


namespace WLC
{
    class WLCCheckReleaseStepDefinition : IStepDefinition
    {
        #region IStepDefinition Members

        public IStep CreateStep(IPropertyReaders properties)
        {
            return new CheckReleaseStep(properties);
        }

        public void DefineSchema(IPropertyDefinitions schema)
        {

            IPropertyDefinition workstationList = schema.AddElementProperty("WLCWorkstationList", WLCWorkstationListElementDefinition.ID);
            workstationList.Description = "The WLCWorkstationList containing all workstations of the shop floor.";
            workstationList.Required = true;

            IPropertyDefinition productStore = schema.AddElementProperty("WLCProductStore", WLCIntermediateProductStoreElementDefinition.ID);
            productStore.Description = "The product store to check for material availability.";
            productStore.Required = true;


            IPropertyDefinition bomElement = schema.AddElementProperty("BOM", WLCBOMElementDefinition.ID);
            bomElement.Description = "The bill of material for the order.";
            bomElement.Required = true;

            IPropertyDefinition sequenceElement = schema.AddElementProperty("Sequence", WLCSequenceElementDefinition.ID);
            sequenceElement.Description = "The sequence of the order.";
            sequenceElement.Required = true;


            IPropertyDefinition orderID = schema.AddExpressionProperty("OrderID", "0");
            orderID.Description = "The unique ID of this order (WLCOrder.ID)";
            orderID.Required = true;


            IPropertyDefinition orderSize = schema.AddExpressionProperty("OrderSize", "0");
            orderSize.Description = "The order size of this order.";
            orderSize.Required = true;


            IPropertyDefinition saveResultTo = schema.AddStateProperty("SaveResultTo");
            saveResultTo.Description = "The discrete state variable to save the result to. If the order can be released, the return value is 1, 0 otherwise.";
            saveResultTo.Required = true;

            IPropertyDefinition saveWLBTo = schema.AddStateProperty("SaveWLBTo");
            saveWLBTo.Description = "The state where to save the WLB before the next order is released.";
            saveWLBTo.Required = true;

            IPropertyDefinition saveBITo = schema.AddStateProperty("SaveBILevelTo");
            saveBITo.Description = "The state where to save the WLB3 BI-Level.";
            saveBITo.Required = true;

            IPropertyDefinition loadAccounts = schema.AddElementProperty("LoadAccount", WLCLoadAccountElementDefinition.ID);
            loadAccounts.Description = "The load accounts element that manages the load accounts for this controller.";
            loadAccounts.Required = true;

            IPropertyDefinition numberInBatch = schema.AddExpressionProperty("NumberInBatch", "4");
            numberInBatch.Description = "The number of orders that should be considered an single batch for WLB2.";
            numberInBatch.Required = false;


            IPropertyDefinition ordersLeft = schema.AddExpressionProperty("OrdersLeftToCheck", String.Empty);
            ordersLeft.Description = "The number of orders left to check after the current order. Important for the WLB2 release method.";
            ordersLeft.Required = false;

            IPropertyDefinition allowExceed = schema.AddExpressionProperty("AllowExceedPercent", String.Empty);
            allowExceed.Description = "The perecentage the LL may be exceeded in WLB3.";
            allowExceed.Required = false;

            IPropertyDefinition tolerancePercent = schema.AddExpressionProperty("ToleranceInPercent", String.Empty);
            tolerancePercent.Description = "The perecentage an order may exceed the tolerance when being released.";
            tolerancePercent.Required = false;



        }

        public int NumberOfExits
        {
            get { return 1; }
        }



        public string Name
        {
            get { return "CheckRelease"; }
        }

        public string Description
        {
            get { return ""; }
        }

        public System.Drawing.Image Icon
        {
            get { return null; }
        }


        public Guid UniqueID
        {
            get { return new Guid("{0471B110-08E9-11DF-8A63-930156D89593}"); }
        }

        #endregion
    }

    class CheckReleaseStep : IStep
    {
        IElementProperty _wlcWorkstationListProp;
        IElementProperty _wlcProductStoreProp;
        IElementProperty _bom;
        IElementProperty _sequence;
        IStateProperty _saveResultProp;
        IStateProperty _saveWLBProp;
        IStateProperty _saveBIProp;
        IPropertyReader _orderIDProp;
        IPropertyReader _orderSizeProp;
        IElementProperty _wlcLoadAccounts;
        IPropertyReader _numberInBatch;
        IPropertyReader _ordersLeft;
        IPropertyReader _allowExceed;
        IPropertyReader _tolerancePercent;







        public CheckReleaseStep(IPropertyReaders bag)
        {
            _wlcWorkstationListProp = (IElementProperty)bag.GetProperty("WLCWorkstationList");
            _wlcProductStoreProp = (IElementProperty)bag.GetProperty("WLCProductStore");
            _bom = (IElementProperty)bag.GetProperty("BOM");
            _sequence = (IElementProperty)bag.GetProperty("Sequence");
            _saveResultProp = (IStateProperty)bag.GetProperty("SaveResultTo");
            _saveWLBProp = (IStateProperty)bag.GetProperty("SaveWLBTo");
            _saveBIProp = (IStateProperty)bag.GetProperty("SaveBILevelTo");
            _orderIDProp = bag.GetProperty("OrderID");
            _orderSizeProp = bag.GetProperty("OrderSize");
            _wlcLoadAccounts = (IElementProperty)bag.GetProperty("LoadAccount");
            _numberInBatch = bag.GetProperty("NumberInBatch");
            _ordersLeft = bag.GetProperty("OrdersLeftToCheck");
            _allowExceed = bag.GetProperty("AllowExceedPercent");
            _tolerancePercent = bag.GetProperty("ToleranceInPercent");

        }

        #region IStep Members

        public ExitType Execute(IStepExecutionContext context)
        {

            WLCLoadAccountElement loadAccounts = (WLCLoadAccountElement)_wlcLoadAccounts.GetElement(context);

            // Get information for the load accounts element for the execution of this step
            String accountMethod;
            String releaseMethod;
            double tolerancePercent;

            accountMethod = loadAccounts._AppliedAccountMethod;
            releaseMethod = loadAccounts._AppliedReleaseMethod;
            tolerancePercent = _tolerancePercent.GetDoubleValue(context);



            // Wrap the current order in an object
            OrderWithID thisOrder = new OrderWithID(GetOrderId(context), context);
            thisOrder.Sequence = (WLCSequenceElement)_sequence.GetElement(context);

            // Get the order size from the property
            double orderSize = (double)_orderSizeProp.GetDoubleValue(context);
            thisOrder.Size = orderSize;

            context.ExecutionInformation.TraceInformation("CHECKRELEASE: Wrapped order with id " + thisOrder.Id);

            // Check for material availability 
            CheckMaterial(thisOrder);


            // Update state variable with WLB Value
            WLCShopFloorState shopFloor = new WLCShopFloorState((WLCWorkstationListElement)_wlcWorkstationListProp.GetElement(context));
            _saveWLBProp.GetState(context).StateValue = shopFloor.GetSumOfDevs();

            // If order's sequence length is only 1, immediately release (only sink is in sequence)
            if (thisOrder.Material && thisOrder.Sequence.GetSequenceCount() <= 1)
            {
                _saveResultProp.GetState(thisOrder.Order).StateValue = 1;
                thisOrder.Order.ExecutionInformation.TraceInformation("CHECKRELEASE: Immediate release because sequene contains no workstations.");
                return ExitType.FirstExit;
            }



            // For these methods only the availability of free workload must be checked
            if (releaseMethod.Equals("wul"))
            {

                return WUL(thisOrder);
            }

            // Simple workload balancing
            else if (releaseMethod.Equals("wlb1"))
            {

                return WLB1(thisOrder);
            }

            // If the method is Davide's method of workload balancing
            else if (releaseMethod.Equals("wlb2"))
            {

                return WLB2(thisOrder);
            }
            // If the method is Fernandes/Silva
            else if (releaseMethod.Equals("wlb3"))
            {

                return WLB3(thisOrder);
            }
            else
            {

                return ExitType.FirstExit;
            }


        }

        private ExitType WLB3(OrderWithID thisOrder)
        {
            if (thisOrder.Material)
            {

                WLCLoadAccountElement loadAccounts = (WLCLoadAccountElement)_wlcLoadAccounts.GetElement(thisOrder.Order);

                String accountMethod;
                String releaseMethod;
                double tolerancePercent;

                accountMethod = loadAccounts._AppliedAccountMethod;
                releaseMethod = loadAccounts._AppliedReleaseMethod;
                tolerancePercent = _tolerancePercent.GetDoubleValue(thisOrder.Order);

                WLCShopFloorState.LoadMethod loadMethod;

                if (accountMethod.Equals("full"))
                    loadMethod = WLCShopFloorState.LoadMethod.Direct;
                else if (accountMethod.Equals("loadconversion"))
                    loadMethod = WLCShopFloorState.LoadMethod.LoadConversion;
                else loadMethod = WLCShopFloorState.LoadMethod.Direct;

                IStepExecutionContext context = thisOrder.Order;

                double allowExceed = _allowExceed.GetDoubleValue(context);
                double currentStepWorkload;


                // Get the sequence of this order from the WLCOrder's repeated property group
                IRepeatingPropertyReader wlcSequence = (IRepeatingPropertyReader)context.AssociatedObject.Properties.GetProperty("WLCSequence");
                WLCWorkstationListElement wlcWorkstationList = (WLCWorkstationListElement)_wlcWorkstationListProp.GetElement(context);

                WLCShopFloorState shopfloor = new WLCShopFloorState(wlcWorkstationList);

                // Calculate WL before release of order
                double maxWorkload = shopfloor.GetMaxWorkload();
                double WLBefore;

                WLBefore = maxWorkload == 0 ? 0 : shopfloor.GetSumOfWorkloads() / (maxWorkload * shopfloor.GetNumberOfWorkstations());



                // Update the state that holds the current BI (WLB3)
                _saveBIProp.GetState(thisOrder.Order).StateValue = WLBefore;

                shopfloor.ReleaseOrder(thisOrder, loadMethod);
                maxWorkload = shopfloor.GetMaxWorkload();
                double WLAfter = shopfloor.GetSumOfWorkloads() / (maxWorkload * shopfloor.GetNumberOfWorkstations());

                thisOrder.Order.ExecutionInformation.TraceInformation("WLB3: BI before: " + WLBefore + ", BI after: " + WLAfter + ".");

                WLCSequenceElement sequence = thisOrder.Sequence;

                // Check if WL improved
                if (WLAfter > WLBefore)
                {
                    for (int i = 0; i < sequence.GetSequenceCount(); i++)
                    {
                        currentStepWorkload = thisOrder.Sequence.GetSequenceProcessingTime(i) * thisOrder.Size;

                        if (shopfloor.GetWorkloadForWorksation(sequence.GetSequenceWorkstationName(i)) + currentStepWorkload > shopfloor.GetWorkloadLimitForWorksation(sequence.GetSequenceWorkstationName(i)) * (1 + (allowExceed / 100)))
                        {
                            thisOrder.Order.ExecutionInformation.TraceInformation("WLB3: Material available but LL exceeded by more than " + allowExceed + " percent.");
                            thisOrder.Release = false;
                            _saveResultProp.GetState(thisOrder.Order).StateValue = 0;
                            return ExitType.FirstExit;
                        }


                    }
                    thisOrder.Order.ExecutionInformation.TraceInformation("WLB3: Material available, WL improved and LL not exceeded.");
                    thisOrder.Release = true;
                    _saveResultProp.GetState(thisOrder.Order).StateValue = 1;
                    return ExitType.FirstExit;


                }
                else
                {
                    thisOrder.Order.ExecutionInformation.TraceInformation("WLB3: Material available but WL does not improve by releasing this order.");
                    thisOrder.Release = false;
                    _saveResultProp.GetState(thisOrder.Order).StateValue = 0;
                    return ExitType.FirstExit;
                }
            }
            else
            {
                // No material
                thisOrder.Order.ExecutionInformation.TraceInformation("WLB3: Material not available and therefore not considered for release.");
                thisOrder.Release = false;
                _saveResultProp.GetState(thisOrder.Order).StateValue = 0;
            }


            return ExitType.FirstExit;
        }

        private void CheckMaterial(OrderWithID thisOrder)
        {
            WLCIntermediateProductStoreElement wlcProductStore = (WLCIntermediateProductStoreElement)_wlcProductStoreProp.GetElement(thisOrder.Order);
            WLCBOMElement bom = (WLCBOMElement)_bom.GetElement(thisOrder.Order);

            // Get the unique order id and cast it to int, and then to string
            string orderID = GetOrderType(thisOrder.Order) + "." + ((int)_orderIDProp.GetDoubleValue(thisOrder.Order)).ToString();

            thisOrder.Order.ExecutionInformation.TraceInformation("CHECKMATERIAL: Checking material for order " + orderID);

            // Get the order size from the property
            double orderSize = (double)_orderSizeProp.GetDoubleValue(thisOrder.Order);

            thisOrder.Material = wlcProductStore.CheckBOM(orderID, bom, orderSize);
            thisOrder.Order.ExecutionInformation.TraceInformation("Material available: " + thisOrder.Material.ToString());

        }

        private string GetOrderType(IStepExecutionContext context)
        {
            string rawType = context.AssociatedObject.ExecutionContext.ToString();
            string[] splitType = rawType.Split(' ');


            return splitType[1];
        }

        private int GetOrderId(IStepExecutionContext context)
        {
            return (int)_orderIDProp.GetDoubleValue(context);
        }

        public ExitType WUL(OrderWithID thisOrder)
        {

            if (thisOrder.Material)
            {
                // Get information for execution
                WLCLoadAccountElement loadAccounts = (WLCLoadAccountElement)_wlcLoadAccounts.GetElement(thisOrder.Order);
                String accountMethod;
                String releaseMethod;
                double tolerancePercent;
                accountMethod = loadAccounts._AppliedAccountMethod;
                releaseMethod = loadAccounts._AppliedReleaseMethod;
                tolerancePercent = _tolerancePercent.GetDoubleValue(thisOrder.Order);


                IStepExecutionContext context = thisOrder.Order;

                // Get the sequence of this order from the WLCOrder's repeated property group
                IRepeatingPropertyReader wlcSequence = (IRepeatingPropertyReader)context.AssociatedObject.Properties.GetProperty("WLCSequence");
                WLCWorkstationListElement wlcWorkstationList = (WLCWorkstationListElement)_wlcWorkstationListProp.GetElement(context);

                double currentWorkstationWorkload;
                double currentWorkstationWorkloadLimit;
                double currentStepWorkload;

                int steps = wlcSequence.GetCount(context.AssociatedObject.ExecutionContext);



                string currentWorkstationName;

                thisOrder.Release = true;

                // Check for all workstations if the workload is not exceeded (first step of release check)

                for (int i = 0; i < steps; i++)
                {
                    // Get the name of the current workstation from the WLCOrder Repeated Property group
                    currentWorkstationName = wlcSequence.GetRow(i, context.AssociatedObject.ExecutionContext).GetProperty("WorkstationName").GetStringValue(context.AssociatedObject.ExecutionContext);

                    if (!currentWorkstationName.Equals("Sink"))
                    {
                        currentStepWorkload = thisOrder.Sequence.GetSequenceProcessingTime(i) * thisOrder.Size;

                        // Get the current workload for this workstation from the list of workstations
                        currentWorkstationWorkload = wlcWorkstationList.GetLoadForWorkstation(currentWorkstationName);
                        currentWorkstationWorkloadLimit = wlcWorkstationList.GetLoadLimitForWorkstation(currentWorkstationName);

                        if (currentWorkstationWorkload + currentStepWorkload >= currentWorkstationWorkloadLimit + (currentWorkstationWorkloadLimit * tolerancePercent / 100))
                        {
                            thisOrder.Release = false;
                            break;
                        }
                    }


                }


            }

            if (thisOrder.Release)
            {
                _saveResultProp.GetState(thisOrder.Order).StateValue = 1;
                thisOrder.Order.ExecutionInformation.TraceInformation("WUL: Material available and releasable.");

            }
            else
            {
                _saveResultProp.GetState(thisOrder.Order).StateValue = 0;

                if (thisOrder.Material)
                    thisOrder.Order.ExecutionInformation.TraceInformation("WUL: Material available but not releasable.");
                else
                    thisOrder.Order.ExecutionInformation.TraceInformation("WUL: Material not available.");
            }

            return ExitType.FirstExit;


        }

        public ExitType WLB2(OrderWithID thisOrder)
        {
            List<OrderWithID> _batchOrders = ((WLCLoadAccountElement)_wlcLoadAccounts.GetElement(thisOrder.Order)).OrderBatchList;

            // Get the execution context for this order
            IStepExecutionContext context = thisOrder.Order;

            // Get the maximum number of order in one batch from the step's props
            int numberInBatch = (int)_numberInBatch.GetDoubleValue(context);

            // If number of orders in a batch has not been reached, and material is available, add this order to the list
            if (_batchOrders.Count < numberInBatch && thisOrder.Material)
            {

                _batchOrders.Add(thisOrder);

                context.ExecutionInformation.TraceInformation("WLB2: Added order to batch (max batch size = " + numberInBatch.ToString() + "), batch size = " + _batchOrders.Count.ToString() + ", orders left = " + _ordersLeft.GetDoubleValue(context).ToString());

            }

            context.ExecutionInformation.TraceInformation("DEBUG: Number in batch: " + _batchOrders.Count + ", ordersLeft: " + _ordersLeft.GetDoubleValue(context));

            // If max number of orders in a bacth is reached or this is the only order left, process
            if (_batchOrders.Count == numberInBatch || _ordersLeft.GetDoubleValue(context) == 1)
            {
                context.ExecutionInformation.TraceInformation("WLB2: Processing batch (max batch size = " + numberInBatch.ToString() + "), batch size = " + _batchOrders.Count.ToString() + ", orders left = " + _ordersLeft.GetDoubleValue(context).ToString());

                ProcessBatchOfOrders(thisOrder);




                // Release marked orders
                foreach (OrderWithID order in _batchOrders)
                {
                    if (order.Release && !(order.Order == context))
                    {
                        // Set the return value to 1
                        _saveResultProp.GetState(order.Order).StateValue = 1;
                        order.Order.ExecutionInformation.TraceInformation("WLB2: Material available and releasable.");
                        order.Order.ProceedOut(ExitType.FirstExit);

                    }
                    else if (!order.Release && !(order.Order == context))
                    {

                        _saveResultProp.GetState(order.Order).StateValue = 0;
                        order.Order.ExecutionInformation.TraceInformation("WLB2: Material available but not releasable.");
                        order.Order.ProceedOut(ExitType.FirstExit);


                    }



                }

                // Very important! Clear batch after processing
                _batchOrders.Clear();


                //  Handle this current context with a return statement
                if (thisOrder.Release)
                {
                    _saveResultProp.GetState(thisOrder.Order).StateValue = 1;
                    thisOrder.Order.ExecutionInformation.TraceInformation("WLB2: Material available and releasable (this).");

                    return ExitType.FirstExit;

                }
                else
                {
                    _saveResultProp.GetState(thisOrder.Order).StateValue = 0;
                    thisOrder.Order.ExecutionInformation.TraceInformation("WLB2: Material available but not releasable (this).");

                    return ExitType.FirstExit;
                }
            }

            if (!thisOrder.Material)
            {
                _saveResultProp.GetState(thisOrder.Order).StateValue = 0;
                thisOrder.Order.ExecutionInformation.TraceInformation("WLB2: Material not available and therefore not considered for release.");
                return ExitType.FirstExit;
            }



            // If none of the above applies, return wait to not let the current order move on to the next step yet
            return ExitType.Wait;

        }

        public ExitType WLB1(OrderWithID thisOrder)
        {

            if (thisOrder.Material)
            {
                // Get information for execution
                WLCLoadAccountElement loadAccounts = (WLCLoadAccountElement)_wlcLoadAccounts.GetElement(thisOrder.Order);
                String accountMethod;
                String releaseMethod;
                double tolerancePercent;
                accountMethod = loadAccounts._AppliedAccountMethod;
                releaseMethod = loadAccounts._AppliedReleaseMethod;
                tolerancePercent = _tolerancePercent.GetDoubleValue(thisOrder.Order);

                WLCShopFloorState.LoadMethod loadMethod;

                if (accountMethod.Equals("full"))
                    loadMethod = WLCShopFloorState.LoadMethod.Direct;
                else if (accountMethod.Equals("loadconversion"))
                    loadMethod = WLCShopFloorState.LoadMethod.LoadConversion;
                else loadMethod = WLCShopFloorState.LoadMethod.Direct;

                IStepExecutionContext context = thisOrder.Order;

                // Get the sequence of this order from the WLCOrder's repeated property group
                IRepeatingPropertyReader wlcSequence = (IRepeatingPropertyReader)context.AssociatedObject.Properties.GetProperty("WLCSequence");
                WLCWorkstationListElement wlcWorkstationList = (WLCWorkstationListElement)_wlcWorkstationListProp.GetElement(context);

                // Create the shop floor state object
                WLCShopFloorState shopFloor = new WLCShopFloorState(wlcWorkstationList);

                double sumSquaredDevBefore = shopFloor.GetSumOfDevs();

                shopFloor.ReleaseOrder(thisOrder, loadMethod);

                double sumSquaredDevNew = shopFloor.GetSumOfDevs();

                // If sum did not improve, do not release
                if (sumSquaredDevNew >= sumSquaredDevBefore)
                    thisOrder.Release = false;
                else
                {

                    thisOrder.Release = true;
                }

            }
            else
            {

                thisOrder.Release = false;
            }

            if (thisOrder.Release)
            {
                _saveResultProp.GetState(thisOrder.Order).StateValue = 1;
                thisOrder.Order.ExecutionInformation.TraceInformation("WLB1: Material available and releasable.");


            }
            else if (!thisOrder.Release)
            {
                _saveResultProp.GetState(thisOrder.Order).StateValue = 0;

                if (thisOrder.Material)
                    thisOrder.Order.ExecutionInformation.TraceInformation("WLB1: Material available but not releasable.");
                else
                    thisOrder.Order.ExecutionInformation.TraceInformation("WLB1: Material not available.");
            }

            return ExitType.FirstExit;


        }


        // This method processes a batch of order, whose execution contexts are stored in the list
        public void ProcessBatchOfOrders(OrderWithID thisOrder)
        {

            // Get information for execution
            WLCLoadAccountElement loadAccounts = (WLCLoadAccountElement)_wlcLoadAccounts.GetElement(thisOrder.Order);
            String accountMethod;
            String releaseMethod;
            double tolerancePercent;
            accountMethod = loadAccounts._AppliedAccountMethod;
            releaseMethod = loadAccounts._AppliedReleaseMethod;
            tolerancePercent = _tolerancePercent.GetDoubleValue(thisOrder.Order);

            List<OrderWithID> _batchOrders = loadAccounts.OrderBatchList;

            WLCShopFloorState.LoadMethod loadMethod;

            if (accountMethod.Equals("full"))
                loadMethod = WLCShopFloorState.LoadMethod.Direct;
            else if (accountMethod.Equals("loadconversion"))
                loadMethod = WLCShopFloorState.LoadMethod.LoadConversion;
            else loadMethod = WLCShopFloorState.LoadMethod.Direct;


            if (_batchOrders.Count > 0)
            {

                thisOrder.Order.ExecutionInformation.TraceInformation("WLB2: Batch contains " + _batchOrders.Count + " element(s).");


                WLCShopFloorState shopState = new WLCShopFloorState((WLCWorkstationListElement)_wlcWorkstationListProp.GetElement(thisOrder.Order));

                double oldDev = shopState.GetSumOfDevs();

                string concSeq = "";

                int numberInBatch = (int)_numberInBatch.GetDoubleValue(thisOrder.Order);

                double smallestDev = Double.PositiveInfinity;
                double currentDev = 0;

                string smallestSequence = "";




                // Permutate all possible sequences (Thanks to http://www.codeproject.com/KB/cs/permutationiterator.aspx)


                foreach (List<OrderWithID> permu in Permutate(_batchOrders, _batchOrders.Count))
                {
                    concSeq = "";

                    thisOrder.Order.ExecutionInformation.TraceInformation("WLB2: Processing permutation: " + permu.ToString());

                    // Release all orders in this permutation
                    foreach (OrderWithID order in permu)
                    {

                        concSeq += order.Id.ToString() + "-";
                        shopState.ReleaseOrder(order, loadMethod);
                        order.Order.ExecutionInformation.TraceInformation("WLB2: Order id in permutation: " + order.Id.ToString());
                    }

                    // Remove trailing '-'
                    concSeq = concSeq.TrimEnd(new char[] { '-' });
                    currentDev = shopState.GetSumOfDevs();


                    if (currentDev < smallestDev)
                    {
                        smallestDev = currentDev;
                        smallestSequence = concSeq;
                        thisOrder.Order.ExecutionInformation.TraceInformation("WLB2: New smallest sequence: " + smallestSequence);
                    }


                    // Un-Release all orders in this permutation
                    foreach (OrderWithID order in permu)
                    {
                        shopState.UnreleaseOrder(order, loadMethod);
                    }

                }

                string[] releaseOrder = smallestSequence.Split(new char[] { '-' });

                // Release orders only if it improves the workload balance of the shop floor
                if (oldDev > smallestDev)
                {

                    // Set orders to releasable if so               
                    for (int i = 0; i < releaseOrder.Length; i++)
                    {



                        foreach (OrderWithID order in _batchOrders)
                        {

                            if (order.Id == int.Parse(releaseOrder[i]))
                            {
                                order.Order.ExecutionInformation.TraceInformation("WLB2: Setting order to releasable.");
                                // Mark the order as releasable
                                order.Release = true;
                                order.Order.ExecutionInformation.TraceInformation("WLB2: Order with ID " + order.Id + ", releasable: " + order.Release);
                            }

                        }

                    }
                }

            }
        }



        // These methods are used to iterate through all permutations of a list
        private static void RotateRight(IList sequence, int count)
        {
            object tmp = sequence[count - 1];
            sequence.RemoveAt(count - 1);
            sequence.Insert(0, tmp);
        }

        private static IEnumerable<IList> Permutate(IList sequence, int count)
        {
            if (count == 1) yield return sequence;
            else
            {
                for (int i = 0; i < count; i++)
                {
                    foreach (var perm in Permutate(sequence, count - 1))
                        yield return perm;
                    RotateRight(sequence, count);
                }
            }
        }

        #endregion

    }



    public class WLCShopFloorState
    {

        Hashtable _workloads = new Hashtable();
        Hashtable _workloadLimits = new Hashtable();

        public enum LoadMethod { Direct, LoadConversion };


        WLCWorkstationListElement wlcWorkstationList;

        public WLCShopFloorState(WLCWorkstationListElement workstationList)
        {
            wlcWorkstationList = workstationList;

            // Initially calculate the deviations
            foreach (string current in workstationList.GetWorkstationNames())
            {
                _workloads[current] = workstationList.GetLoadForWorkstation(current);
                _workloadLimits[current] = workstationList.GetLoadLimitForWorkstation(current);
            }
        }

        public double GetSumOfDevs()
        {
            double result = 0;
            foreach (string current in wlcWorkstationList.GetWorkstationNames())
            {
                result += Math.Pow((double)_workloadLimits[current] - (double)_workloads[current], 2);

            }

            return result;
        }

        public double ReleaseOrder(OrderWithID order, LoadMethod method)
        {
            IStepExecutionContext context = order.Order;

            // Get the sequence of this order from the WLCOrder's repeated property group
            IRepeatingPropertyReader wlcSequence = (IRepeatingPropertyReader)context.AssociatedObject.Properties.GetProperty("WLCSequence");
            int steps = wlcSequence.GetCount(context.AssociatedObject.ExecutionContext);

            double orderSize = (double)context.AssociatedObject.States["OrderSize"].StateValue;

            string currentWorkstationName;
            double currentWorkstationWorkload;
            double currentWorkstationWorkloadLimit;
            double currentStepWorkload;


            if (method.Equals(LoadMethod.Direct))
            {


                for (int i = 0; i < steps; i++)
                {
                    // Get the name of the current workstation from the WLCOrder Repeated Property group
                    currentWorkstationName = wlcSequence.GetRow(i, context.AssociatedObject.ExecutionContext).GetProperty("WorkstationName").GetStringValue(context.AssociatedObject.ExecutionContext);



                    if (!currentWorkstationName.Equals("Sink"))
                    {

                        // Get the current workload and the limit for this workstation from the list of workstations
                        currentWorkstationWorkload = (double)_workloads[currentWorkstationName];
                        currentWorkstationWorkloadLimit = (double)_workloadLimits[currentWorkstationName];

                        // Get the workload this order produces at the current workstation times its size
                        currentStepWorkload = wlcSequence.GetRow(i, context.AssociatedObject.ExecutionContext).GetProperty("ProcessingTime").GetDoubleValue(context.AssociatedObject.ExecutionContext) * orderSize;

                        // Update workload for workstation
                        _workloads[currentWorkstationName] = currentWorkstationWorkload + currentStepWorkload;
                    }
                }


            }
            else if (method.Equals(LoadMethod.LoadConversion))
            {

                double lpg = 0;
                double cf = 1;


                for (int i = 0; i < steps; i++)
                {

                    currentWorkstationName = order.Sequence.GetSequenceWorkstationName(i);
                    if (!currentWorkstationName.Equals("Sink"))
                    {
                        // Get the workload this order produces at upstream workstation and multiply it with the order's size
                        currentStepWorkload = order.Sequence.GetSequenceProcessingTime(i) * orderSize;

                        // Update the workload for the current upstream workstation
                        _workloads[currentWorkstationName] = (double)_workloads[currentWorkstationName] + currentStepWorkload * cf;

                        // Get the LoadPercentage of the current workstation and adjust CF
                        lpg = wlcWorkstationList.GetLoadPercentageForWorkstation(currentWorkstationName);

                        cf = cf * (100 / lpg);

                    }

                }

            }


            return GetSumOfDevs();

        }

        public double UnreleaseOrder(OrderWithID order, LoadMethod method)
        {
            IStepExecutionContext context = order.Order;

            // Get the sequence of this order from the WLCOrder's repeated property group
            IRepeatingPropertyReader wlcSequence = (IRepeatingPropertyReader)context.AssociatedObject.Properties.GetProperty("WLCSequence");
            int steps = wlcSequence.GetCount(context.AssociatedObject.ExecutionContext);

            double orderSize = (double)context.AssociatedObject.States["OrderSize"].StateValue;

            string currentWorkstationName;
            double currentWorkstationWorkload;
            double currentWorkstationWorkloadLimit;
            double currentStepWorkload;

            if (method.Equals(LoadMethod.Direct))
            {
                for (int i = 0; i < steps; i++)
                {
                    // Get the name of the current workstation from the WLCOrder Repeated Property group
                    currentWorkstationName = wlcSequence.GetRow(i, context.AssociatedObject.ExecutionContext).GetProperty("WorkstationName").GetStringValue(context.AssociatedObject.ExecutionContext);

                    if (!currentWorkstationName.Equals("Sink"))
                    {

                        // Get the current workload and the limit 
                        // for this workstation from the list of workstations
                        currentWorkstationWorkload = (double)_workloads[currentWorkstationName];
                        currentWorkstationWorkloadLimit = (double)_workloadLimits[currentWorkstationName];

                        // Get the workload this order produces at the current workstation times its size
                        currentStepWorkload = wlcSequence.GetRow(i, context.AssociatedObject.ExecutionContext).GetProperty("ProcessingTime").GetDoubleValue(context.AssociatedObject.ExecutionContext) * orderSize;

                        // Update squared deviation sum (new)
                        _workloads[currentWorkstationName] = currentWorkstationWorkload - currentStepWorkload;
                    }
                }
            }
            else if (method.Equals(LoadMethod.LoadConversion))
            {

                double lpg = 0;
                double cf = 1;


                for (int i = 0; i < steps; i++)
                {

                    currentWorkstationName = order.Sequence.GetSequenceWorkstationName(i);
                    if (!currentWorkstationName.Equals("Sink"))
                    {
                        // Get the workload this order produces at upstream workstation and multiply it with the order's size
                        currentStepWorkload = order.Sequence.GetSequenceProcessingTime(i) * orderSize;

                        // Update the workload for the current upstream workstation
                        _workloads[currentWorkstationName] = (double)_workloads[currentWorkstationName] - currentStepWorkload * cf;

                        // Get the LoadPercentage of the current workstation and adjust CF
                        lpg = wlcWorkstationList.GetLoadPercentageForWorkstation(currentWorkstationName);

                        cf = cf * (100 / lpg);

                    }

                }
            }

            return GetSumOfDevs();
        }


        public double GetMaxWorkload()
        {
            double result = 0;
            foreach (string key in _workloads.Keys)
            {
                if ((double)_workloads[key] > result)
                    result = (double)_workloads[key];
            }
            return result;
        }

        public double GetSumOfWorkloads()
        {
            double result = 0;
            foreach (string key in _workloads.Keys)
            {
                result += (double)_workloads[key];
            }
            return result;
        }

        public double GetNumberOfWorkstations()
        {
            return _workloads.Count;
        }

        public double GetWorkloadForWorksation(string name)
        {
            if (name.Equals("Sink"))
                return 0;

            return (double)_workloads[name];
        }

        public double GetWorkloadLimitForWorksation(string name)
        {
            if (name.Equals("Sink"))
                return 0;

            return (double)_workloadLimits[name];
        }
    }

    public class OrderWithID
    {
        public int Id { get; set; }
        public IStepExecutionContext Order { get; set; }
        public bool Release { get; set; }
        public bool Material { get; set; }
        public WLCSequenceElement Sequence { get; set; }
        public double Size { get; set; }



        public OrderWithID(int id, IStepExecutionContext context)
        {
            this.Id = id;
            this.Order = context;
            this.Release = false;
            this.Material = false;
            this.Sequence = null;
            this.Size = 1;

        }



    }



}
